<?php

namespace App\Kuafu\Pay\Channel\FuYou;

use App\Kuafu\Pay\BasePay;
use App\Kuafu\Pay\Enum\BasePayEnum;
use App\Kuafu\Pay\Enum\FuYouPayEnum;
use App\Kuafu\Pay\Enum\PayCodeEnum;
use App\Kuafu\Pay\Exceptions\PayException;
use App\Kuafu\Pay\Jobs\RefundPolling;
use App\Kuafu\Pay\Model\PayStreamModel;
use App\Kuafu\Pay\Service\ShopPayService;
use App\Kuafu\Shop\Service\ShopService;
use App\Library\FeiShu\SendFs;
use Illuminate\Support\Facades\Log;

class FuYouPay extends BasePay
{
    protected FuYouBase $fyObj;
    protected           $mchId;
    protected           $orderPrefix = '';

    protected function initConfig(int $shopId, int $sysType)
    {
        $host      = config('pay.fy.host');
        $notifyUrl = config('pay.fy.notify_url');

//        dd($shopId);
        // 通过shopId拿配置信息
        $payConfig = ShopPayService::getPayConfigCache($shopId, PayStreamModel::PAY_CHANNEL_FY);
        if (!$payConfig) {
            $shop     = ShopService::getShopInfo($shopId);
            $shopCode = $shop->shopCode ?? 0;
            $shopName = $shop->name ?? '';
            SendFs::newRobot(SendFs::ROBOT_AUDIT)
                  ->setTitleContent('富友DB支付配置为空',
                      "shopId:{$shopId}\nshopCode:{$shopCode}\nshopName:{$shopName}")
                  ->send();
            throw new PayException(PayCodeEnum::ERR_PAY_CONFIG_EMPTY);
        }

        $this->mchId       = $mchntCd = $payConfig['mch_id'] ?? '';
        $mchntKey          = $payConfig['mch_key'] ?? '';
        $this->orderPrefix = $orderPrefix = $payConfig['order_prefix'] ?? '';
        $reservedFyTermId  = $payConfig['reserved_fy_term_id'] ?? '';

        // 配置错误
        if (!$mchntCd || !$mchntKey || !$orderPrefix) {
            SendFs::newRobot()
                  ->setTitleContent('富友DB支付配置错误',
                      "shopId:{$shopId} mchntCd:{$mchntCd} mchntKey:{$mchntKey} orderPrefix:{$orderPrefix} reservedFyTermId:{$reservedFyTermId}")
                  ->send();
            throw new PayException(PayCodeEnum::ERR_PAY_CONFIG_ERR);
        }

        $this->fyObj = FuYouBase::getIns()->loadMchConfig($host, $mchntCd, $mchntKey, $notifyUrl, $orderPrefix,
            $reservedFyTermId);
    }

    public function faceToFacePay(string $subject, int $amount, string $authCode): array
    {
        // 自动加载payChannelChild
        $payChannelChild = self::checkAuthCode($authCode);
        $this->fyObj->loadChannel($payChannelChild);

        $payNo = $this->getOutTradeNo();
        try {
            $result = $this->fyObj->microPay($payNo, $authCode, $subject, $amount);
        } catch (\Exception $e) {

            // 异常处理切换成『支付中』返回
            if ($e->getCode() != 0) {
                Log::warning($e);
                SendFs::newRobot()->setTitleContent('富友支付 - 被扫异常', "payNo:{$payNo} msg:" . $e->getMessage())
                      ->send();
            }

            $formatRes         = $this->commonReturn([]);
            $formatRes['data'] = [
                'payNo'          => $payNo,
                'thirdPayNo'     => '',
                'tradeStatus'    => BasePayEnum::TRADE_STATUS_WAIT,
                'paySuccessTime' => 0,
            ];

            return $formatRes;
        }

        $formatRes = $this->commonReturn($result);

        $successTime = 0;
        $thirdPayNo  = '';

        if ($formatRes['status']) {

            // 付款成功
            $tradeStatus = BasePayEnum::TRADE_STATUS_OK;

            // 付款成功时间
            $successTime = strtotime($formatRes['oriData']['reserved_txn_fin_ts'] ?? '');

            // 三方流水号
            $thirdPayNo = $formatRes['oriData']['reserved_channel_order_id'];

        } else {
            if (!$formatRes['status'] && $formatRes['code'] == '030010') {

                // 待用户输入密码
                $tradeStatus = BasePayEnum::TRADE_STATUS_WAIT;
            } else {

                // 失败
                $tradeStatus = BasePayEnum::TRADE_STATUS_CLOSED;
            }
        }

        $formatRes['data'] = [
            'payNo'          => $payNo,
            'thirdPayNo'     => $thirdPayNo,
            'tradeStatus'    => $tradeStatus,
            'paySuccessTime' => $successTime,
        ];

        return $formatRes;
    }

    public function closePay(string $outTradeNo): array
    {
        $payChannelChild = PayStreamModel::getPayChannelChildByPayNo($outTradeNo);
        $this->fyObj->loadChannel($payChannelChild);

        $result    = $this->fyObj->close($outTradeNo);
        $formatRes = $this->commonReturn($result);

        // kf订单号
        $formatRes['data']['outTradeNo'] = $outTradeNo;

        return $formatRes;
    }

    public function refund(string $outTradeNo, int $refundAmount)
    {
        $pay = PayStreamModel::getDataByPayNo($outTradeNo);
        if (!$pay) {
            throw new PayException(PayCodeEnum::ERR_PAY_DATA_NOT_EXIST);
        }

        $payChannelChild = $pay->payChannelChild;
        $this->fyObj->loadChannel($payChannelChild);

        $refundOrderId = $this->getOutTradeNo();

        // 原支付总金额
        $originPayAmount = $pay->payPrice;

        try {
            $result = $this->fyObj->commonRefund($outTradeNo, $refundOrderId, $originPayAmount, $refundAmount);

        } catch (\Exception $e) {
            // 生成队列，轮询查询退款结果
            RefundPolling::dispatch($refundOrderId, $outTradeNo)->onQueue(RefundPolling::QUEUE_NAME);
        }
        $formatRes = $this->commonReturn($result);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        $formatRes['data'] = [
            'fundChange'    => true, // 本次退款是否发生了资金变化
            'payNo'         => $outTradeNo, // 原支付流水号
            'refundNo'      => $refundOrderId, // 退款唯一流水号
            'thirdRefundNo' => $formatRes['oriData']['transaction_id'] ?? '',
            'refundAmount'  => $refundAmount,
        ];

        return $formatRes;
    }

    /**
     * 退款测试代码 勿用
     *
     * @param string $outTradeNo
     * @param int $refundAmount
     * @return array
     * @throws PayException
     */
    public function refundTest(string $outTradeNo, int $refundAmount)
    {

        $this->fyObj->loadChannel(PayStreamModel::PAY_CHANNEL_WX);

        $refundOrderId = $this->getOutTradeNo();

        // 原支付总金额
        $originPayAmount = $refundAmount;

        try {
            $result = $this->fyObj->commonRefund($outTradeNo, $refundOrderId, $originPayAmount, $refundAmount);

        } catch (\Exception $e) {
            // 生成队列，轮询查询退款结果
            RefundPolling::dispatch($refundOrderId, $outTradeNo)->onQueue(RefundPolling::QUEUE_NAME);
        }
        $formatRes = $this->commonReturn($result);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        $formatRes['data'] = [
            'fundChange'    => true, // 本次退款是否发生了资金变化
            'payNo'         => $outTradeNo, // 原支付流水号
            'refundNo'      => $refundOrderId, // 退款唯一流水号
            'thirdRefundNo' => $formatRes['oriData']['transaction_id'] ?? '',
            'refundAmount'  => $refundAmount,
        ];

        return $formatRes;
    }

    public function query(string $outTradeNo)
    {
        $payChannelChild = PayStreamModel::getPayChannelChildByPayNo($outTradeNo);
        $this->fyObj->loadChannel($payChannelChild);

        $result    = $this->fyObj->commonQuery($outTradeNo);
        $formatRes = $this->commonReturn($result);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        // 转换支付状态
        $formatRes['data'] = [
            'commonTradeStatus' => $this->convertTradeStatus($formatRes['oriData']['trans_stat'] ?? ''),
            'totalPrice'        => (int)($formatRes['oriData']['order_amt'] ?? 0),
            'payNo'             => $outTradeNo,
            'thirdPayNo'        => $formatRes['oriData']['reserved_channel_order_id'] ?? '',
            'paySuccessTime'    => (int)strtotime($formatRes['oriData']['reserved_txn_fin_ts'] ?? 0),
        ];
        return $formatRes;
    }

    public function refundQuery(string $outTradeNo, string $outRequestNo)
    {
        $payChannelChild = PayStreamModel::getPayChannelChildByPayNo($outTradeNo);
        $this->fyObj->loadChannel($payChannelChild);

        $res       = $this->fyObj->refundQuery($outRequestNo);
        $formatRes = $this->commonReturn($res);

        $formatRes['data'] = [
            'payNo'             => $outTradeNo,
            'refundNo'          => $outRequestNo,
            'thirdRefundNo'     => $formatRes['oriData']['transaction_id'] ?? '',
            'totalAmount'       => $formatRes['oriData']['reserved_refund_amt'] ?? 0,
            'refundStatus'      => $this->convertRefundStatus($formatRes['oriData']['trans_stat'] ?? ''),
            'refundSuccessTime' => time(),
        ];

        return $formatRes;
    }

    public function miniPay(string $orderDesc, int $amount, string $openId)
    {
        $payNo = $this->getOutTradeNo();

        $res       = $this->fyObj->miniPay($amount, $payNo, $openId);
        $formatRes = $this->commonReturn($res);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        $formatRes['data'] = [
            'wx'    => [
                'appId'     => $formatRes['oriData']['sdk_appid'] ?? '',
                'timeStamp' => $formatRes['oriData']['sdk_timestamp'] ?? '',
                'nonceStr'  => $formatRes['oriData']['sdk_noncestr'] ?? '',
                'package'   => $formatRes['oriData']['sdk_package'] ?? '',
                'paySign'   => $formatRes['oriData']['sdk_paysign'] ?? '',
                'signType'  => $formatRes['oriData']['sdk_signtype'] ?? '',
            ],
            'payNo' => $payNo,
        ];

        return $formatRes;
    }

    public function wapPreCreate(int $payChannelChild)
    {
        $this->fyObj->loadChannel($payChannelChild);
        $payNo = $this->getOutTradeNo();

        $res       = $this->fyObj->wapPreCreate(1, $payNo);
        $formatRes = $this->commonReturn($res);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        return $formatRes;
    }

    public function downloadBill(string $date)
    {
        // TODO: Implement downloadBill() method.
    }

    protected function commonReturn($result): array
    {
        $code = $result['result_code'] ?? '';
        $msg  = $result['result_msg'] ?? '网络错误';

        if (!$code || $code != '000000') {
            return self::returnFormatData(false, $code, $msg, $result);
        }

        return self::returnFormatData(true, $code, $msg, $result);
    }

    // 获取订单号
    public function getOutTradeNo(bool $new = false)
    {
        if ($new == false && $this->outTradeNo) {
            return $this->outTradeNo;
        }

        $this->createUniqueTradeNo();

        return $this->outTradeNo = $this->orderPrefix . $this->outTradeNo;
    }

    /**
     * 转换支付状态
     * @param string $apiTradeStatus
     * @return int
     */
    protected function convertTradeStatus(string $apiTradeStatus): int
    {
        return [
            FuYouPayEnum::TRADE_STATUS_WAIT   => BasePayEnum::TRADE_STATUS_WAIT,
            FuYouPayEnum::TRADE_STATUS_WAIT2  => BasePayEnum::TRADE_STATUS_WAIT,
            FuYouPayEnum::TRADE_STATUS_PAYING => BasePayEnum::TRADE_STATUS_WAIT,
            FuYouPayEnum::TRADE_STATUS_CLOSED => BasePayEnum::TRADE_STATUS_CLOSED,
            FuYouPayEnum::TRADE_STATUS_FAIL   => BasePayEnum::TRADE_STATUS_CLOSED,
            FuYouPayEnum::TRADE_STATUS_REFUND => BasePayEnum::TRADE_STATUS_CLOSED,
            FuYouPayEnum::TRADE_STATUS_OK     => BasePayEnum::TRADE_STATUS_OK,
        ][$apiTradeStatus] ?? BasePayEnum::TRADE_STATUS_WAIT;
    }

    /**
     * 转换退款状态
     * @param string $apiRefundStatus
     * @return int
     */
    protected function convertRefundStatus(string $apiRefundStatus): int
    {
        return [
            FuYouPayEnum::REFUND_STATUS_OK    => BasePayEnum::REFUND_STATUS_OK,
            FuYouPayEnum::REFUND_STATUS_CLOSE => BasePayEnum::REFUND_STATUS_ERROR,
            FuYouPayEnum::REFUND_STATUS_ING   => BasePayEnum::REFUND_STATUS_WAIT,
        ][$apiRefundStatus] ?? BasePayEnum::REFUND_STATUS_WAIT;
    }

    /**
     * 获取手续费
     * @param int $payPrice
     * @return float
     */
    public function getFeeAmtByPayPrice(int $payPrice): array
    {

        $rate = FuYouPayEnum::PAYMENT_RATE;

        if (!empty($this->childPayChannel)) {
            switch ($this->childPayChannel) {
                case PayStreamModel::PAY_CHANNEL_CHILD_UNION_PAY:
                    $rate = FuYouPayEnum::PAYMENT_UNION_PAY_RATE;
                    break;
                default:
                    $rate = FuYouPayEnum::PAYMENT_RATE;
            }
        }

        return [
            round($payPrice * $rate), // fee
            number_format($rate, 6, '.', ''), // feeRate
        ];
    }
}
